import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:built_collection/built_collection.dart';
import 'package:mobile_app.core/auth/application/bloc/bloc.dart';
import 'package:mobile_app.core/core/domain/config/config.dart';
import 'package:mobile_app.core/core/domain/entities/email_app.dart';
import 'package:mobile_app.core/core/domain/entities/user.dart';
import 'package:mobile_app.core/core/domain/utils/either_extension.dart';
import 'package:mobile_app.core/email/application/bloc/bloc.dart';
import 'package:mobile_app.core/learning_content/application/bloc/bloc.dart';
import 'package:mobile_app.core/menu_navigator/application/bloc/bloc.dart';
import 'package:mobile_app.core/menu_navigator/application/models/about_page_model.dart';
import 'package:mobile_app.core/menu_navigator/application/models/settings_page_model.dart';
import 'package:mobile_app.core/overlay/application/bloc/bloc.dart';
import 'package:mobile_app.core/purchase_flow_display/application/bloc.dart';
import 'package:mobile_app.core/purchases/application/bloc/bloc.dart';
import 'package:mobile_app.core/root_navigator/application/bloc/bloc.dart';
import 'package:mobile_app.core/root_navigator/application/models/intro_page_model.dart';
import 'package:mobile_app.core/settings/application/bloc/bloc.dart';
import 'package:mobile_app.core/url_launcher/application/bloc/bloc.dart';
import 'package:mobile_app.core/user_info/application/bloc/bloc.dart';
import 'package:mobile_app.home.menu/menu_page/application/models/created_backup_dialog_status_info.dart';
import 'package:mobile_app.home.menu/menu_page/domain/use_cases/create_backup/use_case.dart';
import 'package:mobile_app.home.menu/menu_page/domain/use_cases/delete_user/use_case.dart';
import 'package:mobile_app.home.menu/menu_page/domain/use_cases/get_initial_data/use_case.dart';
import 'package:mobile_app.home.menu/menu_page/domain/use_cases/handle_premium_access_status_outer_update/use_case.dart';
import 'package:mobile_app.home.menu/menu_page/domain/use_cases/handle_user_outer_update/use_case.dart';
import 'package:mobile_app.home.menu/menu_page/domain/use_cases/reset_progress/use_case.dart';
import 'package:mobile_app.home.menu/menu_page/domain/use_cases/restore_backup/use_case.dart';
import 'package:mobile_app.home.menu/menu_page/domain/use_cases/sign_out/use_case.dart';
import 'package:mobile_app.home.menu/menu_page/domain/use_cases/write_to_developers_by_email/use_case.dart';

part 'bloc_state.dart';

class MenuPageBloc extends Cubit<MenuPageBlocState> {
  MenuPageBloc({
    required AuthBloc authBloc,
    required LearningContentBloc learningContentBloc,
    required UserInfoBloc userInfoBloc,
    required MenuNavigatorBloc menuNavigatorBloc,
    required RootNavigatorBloc rootNavigatorBloc,
    required PurchaseFlowDisplayBloc purchaseFlowDisplayBloc,
    required OverlayBloc overlayBloc,
    required EmailBloc emailBloc,
    required SettingsBloc settingsBloc,
    required UrlLauncherBloc urlLauncherBloc,
    required PurchaseBloc purchaseBloc,
    required GetInitialData getInitialData,
    required CreateBackup createBackup,
    required DeleteUser deleteUser,
    required HandlePremiumAccessStatusOuterUpdate handlePremiumAccessStatusOuterUpdate,
    required HandleUserOuterUpdate handleUserOuterUpdate,
    required ResetProgress resetProgress,
    required RestoreBackup restoreBackup,
    required SignOut signOut,
    required WriteToDevelopersByEmail writeToDevelopersByEmail,
  })  : _authBloc = authBloc,
        _learningContentBloc = learningContentBloc,
        _userInfoBloc = userInfoBloc,
        _menuNavigatorBloc = menuNavigatorBloc,
        _rootNavigatorBloc = rootNavigatorBloc,
        _purchaseFlowDisplayBloc = purchaseFlowDisplayBloc,
        _overlayBloc = overlayBloc,
        _settingsBloc = settingsBloc,
        _urlLauncherBloc = urlLauncherBloc,
        _purchaseBloc = purchaseBloc,
        _createBackup = createBackup,
        _deleteUser = deleteUser,
        _handlePremiumAccessStatusOuterUpdate = handlePremiumAccessStatusOuterUpdate,
        _handleUserOuterUpdate = handleUserOuterUpdate,
        _resetProgress = resetProgress,
        _restoreBackup = restoreBackup,
        _signOut = signOut,
        _writeToDevelopersByEmail = writeToDevelopersByEmail,
        super(_getInitialState(
          authBlocState: authBloc.state!,
          userInfoBlocState: userInfoBloc.state!,
          emailBlocState: emailBloc.state!,
          getInitialData: getInitialData,
        )) {
    _authBlocSubscription = authBloc.stream.listen(_onAuthBlocStateChanged);

    _userInfoBlocSubscription = userInfoBloc.streamWithPrevious.listen(_onUserInfoBlocStateChanged);

    _emailBlocSubscription = emailBloc.stream.listen(_onEmailBlocStateChanged);
  }

  final AuthBloc _authBloc;
  final LearningContentBloc _learningContentBloc;
  final UserInfoBloc _userInfoBloc;
  final MenuNavigatorBloc _menuNavigatorBloc;
  final RootNavigatorBloc _rootNavigatorBloc;
  final PurchaseFlowDisplayBloc _purchaseFlowDisplayBloc;
  final OverlayBloc _overlayBloc;
  final SettingsBloc _settingsBloc;
  final UrlLauncherBloc _urlLauncherBloc;
  final PurchaseBloc _purchaseBloc;

  final CreateBackup _createBackup;
  final DeleteUser _deleteUser;
  final HandlePremiumAccessStatusOuterUpdate _handlePremiumAccessStatusOuterUpdate;
  final HandleUserOuterUpdate _handleUserOuterUpdate;
  final ResetProgress _resetProgress;
  final RestoreBackup _restoreBackup;
  final SignOut _signOut;
  final WriteToDevelopersByEmail _writeToDevelopersByEmail;

  late final StreamSubscription<AuthBlocState> _authBlocSubscription;
  late final StreamSubscription<(UserInfoBlocState, UserInfoBlocState?)> _userInfoBlocSubscription;
  late final StreamSubscription<EmailBlocState> _emailBlocSubscription;

  static MenuPageBlocState _getInitialState({
    required AuthBlocState authBlocState,
    required UserInfoBlocState userInfoBlocState,
    required EmailBlocState emailBlocState,
    required GetInitialData getInitialData,
  }) {
    User? user;

    if (authBlocState is AuthorizedAuthBlocState) {
      user = authBlocState.user;
    }

    final result = getInitialData(
      premiumAccessStatus: userInfoBlocState.premiumAccessStatus,
      user: user,
    );

    return MenuPageBlocState(
      user: user,
      purchasePageOpeningActionReasonable: result.purchasePageOpeningActionReasonable,
      subscriptionManagementActionReasonable: result.subscriptionManagementActionReasonable,
      purchaseRestorationActionReasonable: result.purchaseRestorationActionReasonable,
      paymentThroughWebsiteActionReasonable: result.paymentThroughWebsiteActionAvaialble,
      iosPaymentBySimCardPageOpeningActionReasonable:
          result.iosPaymentBySimCardPageOpeningActionReasonable,
      accountDeletionActionReasonable: result.accountDeletionActionReasonable,
      availableEmailApps: emailBlocState.availableEmailApps,
    );
  }

  void _onAuthBlocStateChanged(AuthBlocState authBlocState) {
    User? user;

    if (authBlocState is AuthorizedAuthBlocState) {
      user = authBlocState.user;
    }

    final result = _handleUserOuterUpdate(
      user: user,
    );

    emit(state.copyWith(
      user: () => user,
      accountDeletionActionReasonable: () => result.accountDeletionActionReasonable,
    ));
  }

  void _onUserInfoBlocStateChanged((UserInfoBlocState, UserInfoBlocState?) valueWithPrevious) {
    final (userInfoBlocState, userInfoBlocPreviousState) = valueWithPrevious;

    if (userInfoBlocPreviousState!.premiumAccessStatus == userInfoBlocState.premiumAccessStatus) {
      return;
    }

    final result = _handlePremiumAccessStatusOuterUpdate(
      premiumAccessStatus: userInfoBlocState.premiumAccessStatus,
    );

    emit(state.copyWith(
      purchasePageOpeningActionReasonable: () => result.purchasePageOpeningActionReasonable,
      subscriptionManagementActionReasonable: () => result.subscriptionManagementActionReasonable,
      purchaseRestorationActionReasonable: () => result.purchaseRestorationActionReasonable,
      paymentThroughWebsiteActionReasonable: () => result.paymentThroughWebsiteActionAvaialble,
      iosPaymentBySimCardPageOpeningActionReasonable: () =>
          result.iosPaymentBySimCardPageOpeningActionReasonable,
    ));
  }

  void _onEmailBlocStateChanged(EmailBlocState emailBlocState) {
    emit(state.copyWith(
      availableEmailApps: () => emailBlocState.availableEmailApps,
    ));
  }

  void requestSettingsPageOpening() {
    final pageModel = SettingsPageModel();

    _menuNavigatorBloc.pushPage(
      pageModel: pageModel,
    );
  }

  void requestPurchasePageOpening() {
    _purchaseFlowDisplayBloc.openPurchasePage();
  }

  void requestPurchaseRestoration() {
    _purchaseFlowDisplayBloc.restorePurchases();
  }

  void requestSubscriptionManagement() {
    final String browserUrl;
    String? schemaUrl;

    switch (Config().platform) {
      case Platform.ios:
        browserUrl = 'https://apps.apple.com/account/subscriptions';
        schemaUrl = 'itms-apps://apps.apple.com/account/subscriptions';

      case Platform.android:
        browserUrl = 'https://play.google.com/store/account/subscriptions';
    }

    _urlLauncherBloc.launch(
      schemaUrl: schemaUrl,
      browserUrl: browserUrl,
    );
  }

  void requestWebsitePurchasePageOpening() {
    _urlLauncherBloc.launch(
      browserUrl: Config().websitePurchasePageUrl,
    );
  }

  void requestBackupCreation() async {
    _overlayBloc.showActivityIndicator();

    final result = await _createBackup(
      lexicalItemMap: _learningContentBloc.state!.lexicalItemMap,
      collectionListItems: _learningContentBloc.state!.collectionListItems,
      learningInfo: _userInfoBloc.state!.learningInfo,
    );

    _overlayBloc.hideActivityIndicator();

    final createdBackupDialogStatusInfo = CreatedBackupDialogStatusInfo(
      shown: true,
      backupFileBytes: result.backupFileBytes,
      mimeType: result.mimeType,
      fileName: result.fileName,
    );

    emit(state.copyWith(
      createdBackupDialogStatusInfo: () => createdBackupDialogStatusInfo,
    ));
  }

  void requestBackupRestoration({
    required String filePath,
  }) async {
    _overlayBloc.showActivityIndicator();

    final failureOfResult = await _restoreBackup(
      filePath: filePath,
      lexicalItemMap: _learningContentBloc.state!.lexicalItemMap,
      collectionListItems: _learningContentBloc.state!.collectionListItems,
    );

    if (failureOfResult.isLeft()) {
      _overlayBloc.hideActivityIndicator();

      final failure = failureOfResult.asLeft();

      switch (failure) {
        case InvalidFormatBackupRestorationFailure():
          emit(state.copyWith(
            invalidBackupFormatFailureDialogShown: () => true,
          ));
      }

      return;
    }

    final result = failureOfResult.asRight();

    _overlayBloc.showSuccessIndicator();

    _learningContentBloc.updateWith(
      lexicalItemMap: () => result.lexicalItemMap,
      collectionListItems: () => result.collectionListItems,
    );

    _userInfoBloc.updateLearningInfo(
      learningInfo: result.learningInfo,
    );
  }

  void requestProgressResetStart() {
    emit(state.copyWith(
      progressResetConfirmationDialogShown: () => true,
    ));
  }

  void requestProgressReset() {
    final result = _resetProgress(
      lexicalItemMap: _learningContentBloc.state!.lexicalItemMap,
    );

    _overlayBloc.showSuccessIndicator();

    _learningContentBloc.updateWith(
      lexicalItemMap: () => result.lexicalItemMap,
    );

    _userInfoBloc.updateLearningInfo(
      learningInfo: result.learningInfo,
    );
  }

  void requestTelegramChannelOpening() {
    _urlLauncherBloc.launch(
      schemaUrl: Config().telegramChannelWelcomeMessageSchemaUrl,
      browserUrl: Config().telegramChannelWelcomeMessageBrowserUrl,
    );
  }

  void requestWritingToDevelopersByEmail({
    required EmailApp? emailApp,
  }) {
    _writeToDevelopersByEmail(
      pronunciationSettings: _settingsBloc.state!.pronunciationSettings,
      emailApp: emailApp,
    );
  }

  void requestSigningOut() async {
    final authBlocState = _authBloc.state;

    if (authBlocState is! AuthorizedAuthBlocState) {
      return;
    }

    final refreshToken = authBlocState.accessCredentials.refreshToken;

    final result = _signOut(
      refreshToken: refreshToken,
    );

    emit(state.copyWith(
      user: () => result.user,
      accountDeletionActionReasonable: () => result.accountDeletionActionReasonable,
    ));

    _authBloc.setAuthenticationData(
      accessCredentials: () => result.accessCredentials,
      user: () => result.user,
    );

    _purchaseBloc.obtainPurchases();
  }

  void requestAccountDeletionStart() async {
    emit(state.copyWith(
      accountDeletionConfirmationDialogShown: () => true,
    ));
  }

  void requestAccountDeletion() async {
    _overlayBloc.showActivityIndicator();

    final failureOrResult = await _deleteUser();

    _overlayBloc.hideActivityIndicator();

    if (failureOrResult.isLeft()) {
      final failure = failureOrResult.asLeft();

      switch (failure) {
        case NetworkUserDeletionFailure():
          emit(state.copyWith(
            accountDeletionNetworkFailureDialogShown: () => true,
          ));

        case ServerUserDeletionFailure():
          emit(state.copyWith(
            accountDeletionServerFailureDialogShown: () => true,
          ));

        case UnauthorizedUserDeletionFailure():
          emit(state.copyWith(
            user: () => failure.user,
            accountDeletionActionReasonable: () => failure.accountDeletionActionReasonable,
            accountDeletionUnauthorizedFailureDialogShown: () => true,
          ));

          _authBloc.setAuthenticationData(
            accessCredentials: () => failure.accessCredentials,
            user: () => failure.user,
          );

          _purchaseBloc.obtainPurchases();
      }

      return;
    }

    final result = failureOrResult.asRight();

    emit(state.copyWith(
      user: () => result.user,
      accountDeletionActionReasonable: () => result.accountDeletionActionReasonable,
    ));

    _overlayBloc.showSuccessIndicator();

    _authBloc.setAuthenticationData(
      accessCredentials: () => result.accessCredentials,
      user: () => result.user,
    );

    _purchaseBloc.obtainPurchases();
  }

  void requestAboutPageOpening() {
    final pageModel = AboutPageModel();

    _menuNavigatorBloc.pushPage(
      pageModel: pageModel,
    );
  }

  void requestIntroPageOpening() {
    final pageModel = IntroPageModel();

    _rootNavigatorBloc.pushPageReplacement(
      pageModel: pageModel,
    );
  }

  @override
  Future<void> close() {
    _authBlocSubscription.cancel();
    _userInfoBlocSubscription.cancel();
    _emailBlocSubscription.cancel();
    return super.close();
  }
}
